Нотбук с демо по расчету ЗП¶

Основная задача - построить сервис, который может прогнозировать ЗП, основываясь на данных по профессии, региону размещения, графику работы и другим дотсупным данным.

Требования к модели: модель должна быть интерпетируемой, т.е. мы дожны уметь показать Пользователю основные факторы и их вес в формировании зарплаты. Второе требование - метрика mape <= 0.2.

Основные этапы работы:

  1. Сбор данных. Данные, которые используються в текущей тетради, можно обогатить, использую API с площадок:

hh, труд всем, avito.

  1. EDA. Нужно понять с какими данными работаем, какие из фичей влияют на зарплату. На этом этапе важно понять, правильно ли мы собрали данные? Не ли перекосов в распределениях по локациям? Например у нас 15к вакансий из Мск, 5к вакансий из Питера, а остальные - все остальные регионы РФ. Или у нас в выборке большой перекос в сторону популярыных профессий: кассиров и водителей, а инженеров очень мало.

  2. Обработка данных. Нужно избавиться от выбросов, написать пайплайны для процессинга данных. Сформировать трейн и тест выборки.

  3. Моделирование. Эксперементируем с моделями, для того чтобы выбить на тесте mape <= 0.2, не забывая что модель должна быть интерпретируемой.

  4. Валидация. Проверяем адекватность работы модели.

In [ ]:
from google.colab import drive
drive.mount('/content/drive')
Drive already mounted at /content/drive; to attempt to forcibly remount, call drive.mount("/content/drive", force_remount=True).
In [1]:
%%capture
!pip install https://github.com/pandas-profiling/pandas-profiling/archive/master.zip
In [ ]:
#重新启动内核
import os
os._exit(00)

САМ¶

In [ ]:
import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

#from pandas_profiling import ProfileReport
from ydata_profiling import ProfileReport

Загрузить репрезентативную выборку из набора данных¶

In [ ]:
df = pd.read_csv('/content/drive/MyDrive/研一_项目研讨_预测工资/data_vacancies.csv')
df.head()
Out[ ]:
id custom_position schedule salary_from salary_to salary_pay_type offer_education_id education_name education_is_base education_order_num city_id list_regions work_skills tags_id
0 48202096 Сварщик-сборщик полный рабочий день 60000 120000 NaN 0 любое True 0 2 [4] ['сварочные работы', 'сборка изделий по чертеж... NaN
1 48202097 Сварщик-монтажник полный рабочий день 60000 120000 NaN 0 любое True 0 2 [4] ['монтажные работы', 'строительные работы', 'э... NaN
2 48202098 Слесарь-сборщик полный рабочий день 60000 80000 NaN 0 любое True 0 2 [4] ['работа на фрезерных станках', 'слесарный рем... NaN
3 48202356 Грузчик-упаковщик частичная занятость 30000 35000 NaN 0 любое True 0 1 [3] ['комплектация товара', 'маркировка', 'стрессо... [6, 9]
4 48202357 Грузчик-упаковщик частичная занятость 30000 35000 NaN 0 любое True 0 57 [181, 182, 183, 185, 186, 187, 188, 189, 190, ... ['маркировка', 'стрессоустойчивость', 'погрузо... [6, 9]

Провести предварительный анализ всей выборки и очистку данных¶

In [ ]:
df.shape
Out[ ]:
(19489, 14)
In [ ]:
df.isnull().sum()
Out[ ]:
id                         0
custom_position            0
schedule                   0
salary_from                0
salary_to                  0
salary_pay_type        19383
offer_education_id         0
education_name             0
education_is_base          0
education_order_num        0
city_id                    0
list_regions               0
work_skills                0
tags_id                 5999
dtype: int64
In [ ]:
df['salary_to'].describe()
Out[ ]:
count     19489.000
mean      88490.884
std       55438.161
min       21000.000
25%       51000.000
50%       73000.000
75%      107000.000
max     1200000.000
Name: salary_to, dtype: float64
In [ ]:
df['education_name'].unique()
Out[ ]:
array(['любое', 'среднее', 'высшее', 'среднее профессиональное',
       'неполное высшее'], dtype=object)
In [ ]:
df['schedule'].unique()
Out[ ]:
array(['полный рабочий день', 'частичная занятость', 'удаленная работа',
       'сменный график', 'свободный график', 'вахта'], dtype=object)
In [ ]:
print(f"Всего вакансий в этих данных: {len(df['custom_position']):,}")
Всего вакансий в этих данных: 19,489
In [ ]:
pd.set_option('float_format', lambda x: '{:.3f}'.format(x))
df['salary_from'].describe()
Out[ ]:
count    19489.000
mean     58869.139
std      30248.195
min      20500.000
25%      40000.000
50%      50000.000
75%      70000.000
max     750000.000
Name: salary_from, dtype: float64
In [ ]:
eda_data.columns
Out[ ]:
Index(['id', 'custom_position', 'schedule', 'salary_from', 'salary_to',
       'salary_pay_type', 'offer_education_id', 'education_name',
       'education_is_base', 'education_order_num', 'city_id', 'list_regions',
       'work_skills', 'tags_id', 'graphic'],
      dtype='object')

*Что предположим, если тип зарплаты (salary_pay_type) не указан? Поставить "gros"? Зарплату за тип: "gros", "net".*

Провести статистический анализ оставшихся данных¶

In [ ]:
pd.set_option('display.float_format', lambda x:'%.6f'%x)
df.describe()
Out[ ]:
id salary_from salary_to offer_education_id education_order_num city_id
count 19489.000000 19489.000000 19489.000000 19489.000000 19489.000000 19489.000000
mean 48505170.910411 58869.138848 88490.883935 0.351429 2.474473 22.559495
std 164298.572357 30248.195246 55438.161312 0.970259 6.482314 38.693556
min 48202096.000000 20500.000000 21000.000000 0.000000 0.000000 1.000000
25% 48353880.000000 40000.000000 51000.000000 0.000000 0.000000 1.000000
50% 48513907.000000 50000.000000 73000.000000 0.000000 0.000000 2.000000
75% 48668409.000000 70000.000000 107000.000000 0.000000 0.000000 57.000000
max 48737872.000000 750000.000000 1200000.000000 4.000000 25.000000 272.000000

Провести обработку данных, на основе выводов полученных в прошлых шагах¶

In [ ]:
education_map = {
    'любое': 1,
    'среднее': 2,
    'высшее': 3,
    'среднее профессиональное': 4,
    'неполное высшее': 5
}
eda_data = df

eda_data['education_name'] = eda_data['education_name'].replace(education_map)
In [ ]:
eda_data['graphic'] = eda_data['schedule'].map(lambda x: {
    'полный рабочий день': 1,
    'частичная занятость': 2,
    'удаленная работа': 3,
    'сменный график': 4,
    'свободный график': 5,
    'вахта': 6
}[x])
In [ ]:
eda_data.head()
Out[ ]:
id custom_position schedule salary_from salary_to salary_pay_type offer_education_id education_name education_is_base education_order_num city_id list_regions work_skills tags_id graphic
0 48202096 Сварщик-сборщик полный рабочий день 60000 120000 NaN 0 1 True 0 2 [4] ['сварочные работы', 'сборка изделий по чертеж... NaN 1
1 48202097 Сварщик-монтажник полный рабочий день 60000 120000 NaN 0 1 True 0 2 [4] ['монтажные работы', 'строительные работы', 'э... NaN 1
2 48202098 Слесарь-сборщик полный рабочий день 60000 80000 NaN 0 1 True 0 2 [4] ['работа на фрезерных станках', 'слесарный рем... NaN 1
3 48202356 Грузчик-упаковщик частичная занятость 30000 35000 NaN 0 1 True 0 1 [3] ['комплектация товара', 'маркировка', 'стрессо... [6, 9] 2
4 48202357 Грузчик-упаковщик частичная занятость 30000 35000 NaN 0 1 True 0 57 [181, 182, 183, 185, 186, 187, 188, 189, 190, ... ['маркировка', 'стрессоустойчивость', 'погрузо... [6, 9] 2

Провести корреляционный анализ¶

In [ ]:
numeric_df = eda_data.select_dtypes(include=['int', 'float'])

correlation_matrix = numeric_df.corr()

plt.figure(figsize=(18, 16))
sns.heatmap(
    correlation_matrix,
    annot=True,
    cmap='coolwarm',
    fmt=".2f",
    vmin=-1,
    vmax=1,
    center=0,
    linewidths=0.5)

plt.title('Correlation Matrix of Numeric Columns')
plt.xlabel('X')
plt.ylabel('Y')

plt.xticks(rotation=45, ha='right')

plt.show()
In [ ]:
df.isnull().sum()
Out[ ]:
id                         0
custom_position            0
schedule                   0
salary_from                0
salary_to                  0
salary_pay_type        19383
offer_education_id         0
education_name             0
education_is_base          0
education_order_num        0
city_id                    0
list_regions               0
work_skills                0
tags_id                 5999
dtype: int64
In [ ]:
test = df[df['salary_pay_type'].notna()]
test.head()
Out[ ]:
id custom_position schedule salary_from salary_to salary_pay_type offer_education_id education_name education_is_base education_order_num city_id list_regions work_skills tags_id
531 48223566 Менеджер по продажам окон и жалюзи полный рабочий день 60000 100000 net 4 высшее True 10 57 [1269] ['оформление заказов'] NaN
704 48230735 Менеджер по продажам b2b полный рабочий день 60000 120000 net 3 неполное высшее True 15 2 [4] ['входящие звонки'] [9]
817 48232436 Дизайнер кухонь свободный график 100000 200000 net 4 высшее True 10 1 [3] ['1'] [9]
954 48237920 Инженер по ремонту МФУ, плотеров и принтеров полный рабочий день 55000 85000 net 0 любое True 0 1 [3] ['Опыт ремонта оргтехники'] NaN
1504 48260331 Менеджер записи на пробный урок в международну... полный рабочий день 30000 50000 net 0 любое True 0 57 [204] ['входящие звонки', 'исходящие звонки'] NaN
In [ ]:
corr_matrix=df.corr()
plt.subplots(figsize=(15,8))
fig=sns.heatmap(corr_matrix, annot=True, square=True, cmap='RdBu', fmt='.4g') #annot为热力图上显示数据, fmt='.2g'为数据保留两位有效数字, square呈现正方形, vmax最大值为1
fig;
<ipython-input-46-7a11d84963f5>:1: FutureWarning: The default value of numeric_only in DataFrame.corr is deprecated. In a future version, it will default to False. Select only valid columns or specify the value of numeric_only to silence this warning.
  corr_matrix=df.corr()

Построить Графики взаимодействия полученных данных с целевым показателем¶

In [ ]:
report=ProfileReport(df)
report
Summarize dataset:   0%|          | 0/5 [00:00<?, ?it/s]
Generate report structure:   0%|          | 0/1 [00:00<?, ?it/s]
Render HTML:   0%|          | 0/1 [00:00<?, ?it/s]
Out[ ]:

In [ ]:
sample_df = df.sample(n=100)
Out[ ]:
id custom_position schedule salary_from salary_to salary_pay_type offer_education_id education_name education_is_base education_order_num city_id list_regions work_skills tags_id graphic
13441 48634020 Сборщик кухонной мебели свободный график 170000 200000 NaN 0 1 1 0 1 [3] ['сборка мебели', 'пунктуальность', 'стрессоус... NaN 5
16886 48704160 Рубщик мяса (г. Сосновый Бор) сменный график 44250 93000 NaN 0 1 1 0 102 [174] ['Разруб мяса'] NaN 4
13552 48638065 Фотограф полный рабочий день 35000 80000 NaN 0 1 1 0 2 [4] ['съемка детей', 'портретная съемка', 'Adobe P... NaN 1
8731 48481735 Продавец-кассир (ул. Советская дом 47) сменный график 44206 48627 NaN 0 1 1 0 57 [1220] ['без опыта', 'доброжелательность'] [9] 4
8796 48485078 Курьер частичная занятость 25000 100000 NaN 0 1 1 0 1 [3] ['трудолюбие', 'вежливость', 'коммуникабельнос... [6, 9] 2
In [ ]:
import matplotlib.pyplot as plt
import seaborn as sns


def create_boxplot(data, x_column, y_column, title, figsize=(18, 8)):
  """Creates a boxplot using Seaborn.

  Args:
    data: The data to use to create the boxplot.
    x_column: The name of the x-axis column.
    y_column: The name of the y-axis column.
    title: The title of the boxplot.
    figsize: The figure size of the plot.
  """

  plt.figure(figsize=figsize)
  sns.boxplot(
      x=x_column,
      y=y_column,
      showmeans=True,
      notch=True,
      whis=1.5,
      data=data,
  )
  plt.xticks(rotation=-5)
  plt.title(title)
  plt.show()


# Create the boxplots.
create_boxplot(sample_df, "schedule", "salary_to", "Salary Distribution by Schedule Level")
create_boxplot(sample_df, "schedule", "salary_from", "Salary Distribution by Schedule Level")
create_boxplot(sample_df, "education_name", "salary_to", "Salary Distribution by Education Level")
create_boxplot(sample_df, "education_name", "salary_from", "Salary Distribution by Education Level")
In [ ]:
plt.figure(figsize=(18, 18))
sns.boxplot(
    x='schedule',
    y='salary_from',
    showmeans=True,
    notch=True,
    whis=1.5,
    data=sample_df)
plt.xticks(rotation=-25)
plt.title('Salary Distribution by Schedule Level')
plt.show()
In [ ]:
def create_scatter_plot(data, x_column, y_column, title, palette):
  """Creates a scatter plot using Seaborn.

  Args:
    data: The data to use to create the scatter plot.
    x_column: The name of the x-axis column.
    y_column: The name of the y-axis column.
    title: The title of the scatter plot.
    palette: The color palette to use for the scatter plot.
  """

  sns.scatterplot(
      x=x_column,
      y=y_column,
      alpha=0.5,
      palette=palette,
      data=data,
  )

  plt.xlabel(x_column)
  plt.ylabel(y_column)
  plt.title(title)
  plt.show()


# Create the scatter plots.
create_scatter_plot(eda_data, "education_name", "salary_to", "Scatter Plot: Salary vs. Education Level (Salary To)", "viridis")
create_scatter_plot(eda_data, "education_name", "salary_from", "Scatter Plot: Salary vs. Education Level (Salary From)", "inferno")
<ipython-input-65-0a3e98970c34>:12: UserWarning: Ignoring `palette` because no `hue` variable has been assigned.
  sns.scatterplot(
<ipython-input-65-0a3e98970c34>:12: UserWarning: Ignoring `palette` because no `hue` variable has been assigned.
  sns.scatterplot(

Сделать выводы на основе проведенного анализа¶

  • График показывает, что существует положительная корреляция между уровнем образования и заработной платой. Это означает, что люди с более высоким уровнем образования, скорее всего, будут зарабатывать больше. Однако корреляция не идеальна.

  • Некоторые люди с более низким уровнем образования зарабатывают больше, чем люди с более высоким уровнем образования, но в целом люди с более высоким уровнем образования зарабатывают больше.

  • Также стоит отметить, что распределение заработной платы для каждого уровня образования асимметрично вправо, что означает, что больше людей зарабатывают более низкую заработную плату, чем людей, зарабатывающих более высокую заработную плату.

-¶

In [ ]:
report.to_widgets()
/usr/local/lib/python3.10/dist-packages/ydata_profiling/profile_report.py:507: UserWarning: Ipywidgets is not yet fully supported on Google Colab (https://github.com/googlecolab/colabtools/issues/60).As an alternative, you can use the HTML report. See the documentation for more information.
  warnings.warn(
VBox(children=(Tab(children=(Tab(children=(GridBox(children=(VBox(children=(GridspecLayout(children=(HTML(valu…
In [ ]:
report.to_notebook_iframe()

Анализ данных¶

In [ ]:
eda_data['education_is_base'] = eda_data['education_is_base'].astype(int)
In [ ]:
eda_data.columns
Out[ ]:
Index(['id', 'custom_position', 'schedule', 'salary_from', 'salary_to',
       'salary_pay_type', 'offer_education_id', 'education_name',
       'education_is_base', 'education_order_num', 'city_id', 'list_regions',
       'work_skills', 'tags_id', 'graphic'],
      dtype='object')
In [ ]:
columns_to_pick = ['id', 'custom_position', 'salary_from', 'salary_to', 'offer_education_id', 'education_name',
       'education_is_base', 'education_order_num', 'city_id', 'education_level', 'schedule']
In [ ]:
eda_data = eda_data.filter(columns_to_pick)
In [ ]:
eda_data
Out[ ]:
id custom_position salary_from salary_to offer_education_id education_name education_is_base education_order_num city_id schedule
0 48202096 Сварщик-сборщик 60000 120000 0 1 1 0 2 полный рабочий день
1 48202097 Сварщик-монтажник 60000 120000 0 1 1 0 2 полный рабочий день
2 48202098 Слесарь-сборщик 60000 80000 0 1 1 0 2 полный рабочий день
3 48202356 Грузчик-упаковщик 30000 35000 0 1 1 0 1 частичная занятость
4 48202357 Грузчик-упаковщик 30000 35000 0 1 1 0 57 частичная занятость
... ... ... ... ... ... ... ... ... ... ...
19484 48737855 Кладовщик 45000 70000 2 4 1 20 1 полный рабочий день
19485 48737859 Кассир 35000 58000 0 1 1 0 1 сменный график
19486 48737860 Инженер по медицинской технике 77000 77000 4 3 1 10 1 полный рабочий день
19487 48737871 Автомеханик-автослесарь 80000 120000 0 1 1 0 2 полный рабочий день
19488 48737872 Автомеханик-автослесарь 80000 120000 0 1 1 0 102 полный рабочий день

19489 rows × 10 columns

Distributions

Categorical distributions

2-d distributions

Time series

Values

Faceted distributions

In [ ]:
 
In [ ]:
 
In [ ]:
 

Данные по вакансиям¶

В качестве y будем использовать поле salary_from

Спикок кодировок регионов и тэгов - в отдельном справочнике.

In [ ]:
import os
import pandas as pd
import numpy as np
In [ ]:
import matplotlib.pyplot as plt
import seaborn as sns
In [ ]:
sns.set_theme(style="whitegrid")
In [ ]:
plt.style.use('ggplot')
In [ ]:
# DATA_FOLDER = os.path.join('data') # путь к директории с данными
In [ ]:
from google.colab import files
uploaded = files.upload()
Upload widget is only available when the cell has been executed in the current browser session. Please rerun this cell to enable.
In [ ]:
data = pd.read_csv(os.path.join(DATA_FOLDER, 'data_vacancies.csv'))

data.head(3)
Out[ ]:
id custom_position schedule salary_from salary_to salary_pay_type offer_education_id education_name education_is_base education_order_num city_id list_regions work_skills tags_id
0 48202096 Сварщик-сборщик полный рабочий день 60000 120000 NaN 0 любое True 0 2 [4] ['сварочные работы', 'сборка изделий по чертеж... NaN
1 48202097 Сварщик-монтажник полный рабочий день 60000 120000 NaN 0 любое True 0 2 [4] ['монтажные работы', 'строительные работы', 'э... NaN
2 48202098 Слесарь-сборщик полный рабочий день 60000 80000 NaN 0 любое True 0 2 [4] ['работа на фрезерных станках', 'слесарный рем... NaN
In [ ]:
 
In [ ]:
data.describe(include='all').T['count']
Out[ ]:
id                     19489.0
custom_position          19489
schedule                 19489
salary_from            19489.0
salary_to              19489.0
salary_pay_type            106
offer_education_id     19489.0
education_name           19489
education_is_base        19489
education_order_num    19489.0
city_id                19489.0
list_regions             19489
work_skills              19489
tags_id                  13490
Name: count, dtype: object
In [ ]:
print(f"Общее кол-во вакансий - {len(data):,}.")
Общее кол-во вакансий - 19,489.
In [ ]:
data['salary_from'].describe()
Out[ ]:
count     19489.000000
mean      58869.138848
std       30248.195246
min       20500.000000
25%       40000.000000
50%       50000.000000
75%       70000.000000
max      750000.000000
Name: salary_from, dtype: float64
In [ ]:
 

Профессии¶

In [ ]:
top20_profs = data['custom_position'].value_counts(dropna=False).nlargest(20)

top20_profs
Out[ ]:
Продавец-кассир                                                     409
Менеджер по продажам                                                290
Продавец-консультант                                                238
Курьер                                                              193
Охранник                                                            134
Повар                                                               130
Разнорабочий                                                        127
Водитель по доставке документов                                     118
Грузчик                                                             118
Комплектовщик                                                       112
Работник торгового зала                                             105
Продавец                                                             96
Кладовщик                                                            96
Менеджер по работе с клиентами                                       95
Оператор call-центра / Менеджер по работе с клиентами (удаленно)     95
Оператор call-центра                                                 94
Мерчандайзер-грузчик                                                 94
Оператор входящих звонков                                            87
Водитель-экспедитор                                                  87
Оператор call-центра (удаленно)                                      78
Name: custom_position, dtype: int64
In [ ]:
 
In [ ]:
data[data['city_id'] == 1]['custom_position'].value_counts(dropna=False).nlargest(20)
Out[ ]:
Менеджер по продажам                                                                       179
Продавец-кассир                                                                            166
Курьер                                                                                     123
Продавец-консультант                                                                       115
Повар                                                                                       86
Оператор входящих звонков                                                                   80
Копирайтер                                                                                  72
Контент-менеджер                                                                            65
Менеджер социальных сетей                                                                   58
Помощник копирайтера                                                                        55
Работник торгового зала                                                                     55
Мерчандайзер-грузчик                                                                        54
Оператор call-центра                                                                        52
Грузчик                                                                                     52
Охранник                                                                                    50
Уборщик/ца                                                                                  49
Помощник разработчика сайтов                                                                49
Кладовщик                                                                                   48
Помощник контент-менеджера                                                                  47
Менеджер по работе с клиентами магазина бытовой техники и электроники (Входящие звонки)     47
Name: custom_position, dtype: int64
In [ ]:
 
In [ ]:
data[data['city_id'] == 2]['custom_position'].value_counts(dropna=False).nlargest(20)
Out[ ]:
Продавец-кассир                   67
Менеджер по продажам              50
Охранник                          44
Продавец-консультант              44
Курьер                            42
Домработница приходящая           35
Уборщик, Уборщица                 28
Разнорабочий                      26
Кладовщик                         26
Продавец                          21
Уборщик/Уборщица                  21
Работник торгового зала           19
Грузчик                           18
Менеджер по работе с клиентами    18
Бариста                           17
Сборщик интернет-заказов          17
Администратор                     15
Электромонтажник                  15
Заместитель директора магазина    15
Автомеханик-автослесарь           15
Name: custom_position, dtype: int64
In [ ]:
 
In [ ]:
_df = data[data['custom_position'].isin(top20_profs.index)]
profession_ranking = list(top20_profs.index)

f, ax = plt.subplots(figsize=(12,5))
ax = sns.boxenplot(x="custom_position", y="salary_from",
              color="gray",  palette="Set3", order=profession_ranking,
              scale="linear", data=_df, linewidth=0.5)

ax.tick_params(axis='x', rotation=90)
ax.set_title("Зарплата по ТОП 20 професиям", fontsize=14)

means = _df.groupby("custom_position")["salary_from"].mean().loc[profession_ranking]
_ = plt.plot(range(len(profession_ranking)), means, marker="o", color="green", markersize=6, linestyle="--")
In [ ]:
 
In [ ]:
 

Расчет ЗП только по професии¶

In [ ]:
from sklearn.model_selection import train_test_split
In [ ]:
data_train, data_test, y_train, y_test = train_test_split(data[['custom_position']],
                                                          data[['salary_from']],
                                                          test_size=0.2,
                                                          random_state=42)
In [ ]:
data_train.shape, data_test.shape
Out[ ]:
((15591, 1), (3898, 1))
In [ ]:
 

Представим професии в виде вектора, использую FastText¶

In [ ]:
import fasttext
import fasttext.util
In [ ]:
import numpy as np
from scipy import spatial
In [ ]:
ft = fasttext.load_model('../cc.ru.300.bin')
Warning : `load_model` does not return WordVectorModel or SupervisedModel any more, but a `FastText` object which is very similar.
In [ ]:
fasttext.util.reduce_model(ft, 100) # уменьшаем размерность вектора
Out[ ]:
<fasttext.FastText._FastText at 0x7fa00ae37850>
In [ ]:
ft.get_dimension()
Out[ ]:
100
In [ ]:
ft.get_word_vector('водитель')[:10]
Out[ ]:
array([-0.02232989, -0.02558348,  0.0685254 ,  0.04912743,  0.01441588,
        0.01026478, -0.10471214,  0.03264587,  0.05108723,  0.11439214],
      dtype=float32)
In [ ]:
def get_cosine_similarity(a: list, b: list):
    return 1 - spatial.distance.cosine(a, b)
In [ ]:
def get_vector(s, model=ft):
    s = s.lower().strip()

    return model.get_word_vector(s)
In [ ]:
get_vector('Водитель ')[:10]
Out[ ]:
array([-0.02232989, -0.02558348,  0.0685254 ,  0.04912743,  0.01441588,
        0.01026478, -0.10471214,  0.03264587,  0.05108723,  0.11439214],
      dtype=float32)
In [ ]:
get_cosine_similarity(get_vector('водитель'), get_vector('таксист'))
Out[ ]:
0.8104820251464844
In [ ]:
get_cosine_similarity(get_vector('тракторист'), get_vector('фотомодель'))
Out[ ]:
0.3793732523918152
In [ ]:
 

Пайплайн для обработки данных¶

In [ ]:
from sklearn.preprocessing import FunctionTransformer
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.pipeline import Pipeline

from sklearn.neighbors import KNeighborsRegressor
from sklearn.model_selection import KFold, GridSearchCV

from sklearn.metrics import r2_score, mean_absolute_percentage_error
In [ ]:
 
In [ ]:
class DataFrameSelector(BaseEstimator, TransformerMixin):
    def __init__(self, attribute_names):
        self.attribute_names = attribute_names
    def fit(self, X, y=None):
        return self
    def transform(self, X):
        return X[self.attribute_names]
In [ ]:
 
In [ ]:
class ProcessingTextFeatures(BaseEstimator, TransformerMixin):
    def __init__(self):

        pass
    def fit(self, X, y=None):
        return self

    @staticmethod
    def text_processing(s):
        if s:
            return s.lower().strip()
        else:
            return None

    def transform(self, X):
        for a in X.columns:
            X[a] = X[a].apply(lambda x: self.text_processing(x))
        return X
In [ ]:
 
In [ ]:
class TextEmbeddings(BaseEstimator, TransformerMixin):
    def __init__(self,  lang_model):
        self.lang_model = lang_model
    def fit(self, X, y=None):
        return self

    @staticmethod
    def get_features_from_vector(vectors):
        df = pd.DataFrame()
        for v in vectors:
            df = pd.concat([df, pd.DataFrame(v).T])
        _cols = [f"feature_{c}" for c in df.columns]
        df.columns = _cols
        return df


    def transform(self, X):
        X['vector_professions'] = X.iloc[:,0].apply(
        lambda words: np.mean([get_vector(word) for word in words.split(" ")], axis=0))

        return self.get_features_from_vector(X['vector_professions'])
In [ ]:
 
In [ ]:
profession_embedding_pipeline = Pipeline([
        ("select_features", DataFrameSelector(attribute_names=["custom_position"])),
        ("processing_text_features", ProcessingTextFeatures()),
        ("get_profession_embeddings", TextEmbeddings(lang_model=ft)),

    ])
In [ ]:
X_train = profession_embedding_pipeline.fit_transform(data_train)
X_train.head(3)
Out[ ]:
feature_0 feature_1 feature_2 feature_3 feature_4 feature_5 feature_6 feature_7 feature_8 feature_9 ... feature_90 feature_91 feature_92 feature_93 feature_94 feature_95 feature_96 feature_97 feature_98 feature_99
0 0.015912 0.001667 0.024425 -0.033466 0.028346 0.002949 -0.009210 0.010261 0.012796 0.009221 ... 0.002755 -0.005422 0.022568 -0.017908 0.009551 0.033006 -0.006285 0.021489 -0.007438 -0.028621
0 0.012033 0.064572 0.030388 -0.082866 -0.061539 0.052734 -0.057564 0.055990 -0.013068 -0.059835 ... -0.006975 0.030193 -0.012232 -0.028487 -0.031524 -0.013782 0.049508 0.043902 0.024200 0.045752
0 -0.002965 0.054473 0.010346 -0.056326 -0.138993 0.012678 0.025266 0.074584 0.061354 -0.024473 ... 0.015798 0.037486 0.035533 -0.017324 -0.044231 -0.040195 0.052769 0.008908 0.012552 0.083026

3 rows × 100 columns

In [ ]:
 
In [ ]:
num_folds = 5
seed = 7
scoring = 'neg_mean_absolute_error'

neighbors = [1, 3, 5, 7, 9, 15]
param_grid = dict(n_neighbors=neighbors)
model = KNeighborsRegressor()
kfold = KFold(n_splits=num_folds)
grid = GridSearchCV(estimator=model, param_grid=param_grid, scoring=scoring, cv=kfold)
grid_result = grid.fit(X_train, y_train)
print("Best: %f using %s" % (-1 * grid_result.best_score_, grid_result.best_params_))
means = grid_result.cv_results_['mean_test_score']
stds = grid_result.cv_results_['std_test_score']
params = grid_result.cv_results_['params']
for mean, stdev, param in zip(means, stds, params):
    print("MAE: %f (%f) with: %r" % (-1 * mean, stdev, param))
Best: 13900.328053 using {'n_neighbors': 5}
MAE: 15016.689200 (399.050735) with: {'n_neighbors': 1}
MAE: 14012.989705 (346.416102) with: {'n_neighbors': 3}
MAE: 13900.328053 (361.543882) with: {'n_neighbors': 5}
MAE: 14064.296092 (295.366199) with: {'n_neighbors': 7}
MAE: 14186.445977 (260.040995) with: {'n_neighbors': 9}
MAE: 14561.963819 (224.822423) with: {'n_neighbors': 15}
In [ ]:
 
In [ ]:
 
In [ ]:
#evaluation - baselines
num_folds = 5
seed = 7
scoring = 'neg_mean_absolute_error'
models = []
models.append(('LR', LinearRegression()))
models.append(('RidgeRegression', Ridge()))
models.append(('LassoRegression', Lasso()))
models.append(('KNNRegression', KNeighborsRegressor()))
models.append(('DecisionTreeRegressor', DecisionTreeRegressor()))


results = []
names = []
for name, model in models:
    kfold = KFold(n_splits=num_folds, random_state=seed, shuffle=True)

    # умножаем на (-1) из-за особеностей работы cross_val_score
    cv_results = -1 * cross_val_score(model, X_train, y_train, cv=kfold, scoring=scoring)

    results.append(cv_results)
    names.append(name)
    msg = "%s %f %f " % (name, cv_results.mean(), cv_results.std())
    print(msg)
In [ ]:
best_knn = grid.best_estimator_
Out[ ]:
KNeighborsRegressor()
In [ ]:
preds = best_knn.predict(profession_embedding_pipeline.fit_transform(data_test))

mape = mean_absolute_percentage_error(y_test, preds)

print(f"mape = {round(mape, 3)}")
mape = 0.227
In [ ]: